﻿using System;

namespace Raven
{
    public class RavenBot : MovingEntity
    {
        private enum Status
        {
            Alive,
            Dead,
            Spawning,
        }

        private RavenGame _world;
        private DisplayObject _displayObject;

        private Status _status;
        private GoalThink _brain;
        private RavenSteering _steering;
        private RavenPathPlanner _pathPlanner;
        private TargetSystem _targetSystem;
        private RavenSensoryMemory _sensoryMem;
        private RavenWeaponSystem _weaponSystem;

        //A regulator object limits the update frequency of a specific AI component
        private Regulator _weaponSelectionRegulator;
        private Regulator _goalArbitrationRegulator;
        private Regulator _targetSelectionRegulator;
        private Regulator _triggerTestRegulator;
        private Regulator _vsionUpdateRegulator;

        private int _health;
        private int _maxHealth;
        private Vector2 _facing;
        private Fix _fieldOfView;
        private bool _possessed;

        public int Health()
        {
            return _health;
        }


        public int MaxHealth()
        {
            return _maxHealth;
        }


        public override void Destroy()
        {
            _displayObject.Destroy();
        }

        public RavenBot(RavenGame world, Vector2 position) : base(Raven.EntityType.Bot, position,
            Constants.Bot_BRadius, Vector2.zero, Constants.Bot_MaxSpeed,
            new Vector2(1, 0), Constants.Bot_Mass,
            Constants.Bot_MaxHeadTurnRate, Constants.Bot_MaxForce)
        {
            _maxHealth = Constants.Bot_MaxHealth;
            _fieldOfView = FixMath.DegsToRads(Constants.Bot_FOV);
            _status = Status.Spawning;
            _world = world;
            _facing = _heading;
            _pathPlanner = new RavenPathPlanner(this);
            _steering = new RavenSteering(world, this);
            _targetSystem = new TargetSystem(this);
            _sensoryMem = new RavenSensoryMemory(this, Constants.Bot_MemorySpan);
            _weaponSystem = new RavenWeaponSystem(this, Constants.Bot_ReactionTime, Constants.Bot_AimAccuracy,
                Constants.Bot_AimPersistance);

            //create the goal queue
            _brain = new GoalThink(this);

            //create the regulators
            _weaponSelectionRegulator = new Regulator(_world, Constants.Bot_WeaponSelectionFrequency);
            _goalArbitrationRegulator = new Regulator(_world, Constants.Bot_GoalAppraisalUpdateFreq);
            _targetSelectionRegulator = new Regulator(_world, Constants.Bot_TargetingUpdateFreq);
            _triggerTestRegulator = new Regulator(_world, Constants.Bot_TriggerUpdateFreq);
            _vsionUpdateRegulator = new Regulator(_world, Constants.Bot_VisionUpdateFreq);

            CreateDisplayObject();
        }

        public GoalThink GetBrain()
        {
            return _brain;
        }

        public RavenPathPlanner GetPathPlanner()
        {
            return _pathPlanner;
        }


        public RavenSteering GetSteering()
        {
            return _steering;
        }

        public TargetSystem GetTargetSys()
        {
            return _targetSystem;
        }

        public RavenWeaponSystem GetWeaponSys()
        {
            return _weaponSystem;
        }

        public RavenSensoryMemory GetSensoryMem()
        {
            return _sensoryMem;
        }

        public RavenGame GetWorld()
        {
            return _world;
        }

        public Vector2 Facing()
        {
            return _facing;
        }

        public bool isSpawning()
        {
            return _status == Status.Spawning;
        }

        public bool isDead()
        {
            return _status == Status.Dead;
        }

        public bool isAlive()
        {
            return _status == Status.Alive;
        }

        public void SetSpawning()
        {
            _status = Status.Spawning;
        }

        public void SetDead()
        {
            _status = Status.Dead;
        }

        public void SetAlive()
        {
            _status = Status.Alive;
        }

        public bool isPossessed()
        {
            return _possessed;
        }

        public void CreateDisplayObject()
        {
            _displayObject = DisplayObjectFactory.CreateDisplayObject(this);
            _displayObject.Start();
        }

        public void Update()
        {
            //process the currently active goal. Note this is required even if the bot
            //is under user control. This is because a goal is created whenever a user 
            //clicks on an area of the map that necessitates a path planning request.
            UnityEngine.Profiling.Profiler.BeginSample("Brain");
            _brain.Process();
            UnityEngine.Profiling.Profiler.EndSample();
            //Calculate the steering force and update the bot's velocity and position
            UnityEngine.Profiling.Profiler.BeginSample("UpdateMovement");
            UpdateMovement();
            UnityEngine.Profiling.Profiler.EndSample();
            UnityEngine.Profiling.Profiler.BeginSample("Regulator");
            //if the bot is under AI control but not scripted
            if (!isPossessed())
            {
                //examine all the opponents in the bots sensory memory and select one
                //to be the current target
                if (_targetSelectionRegulator.isReady())
                {
                    _targetSystem.Update();
                }

                //appraise and arbitrate between all possible high level goals
                if (_goalArbitrationRegulator.isReady())
                {
                    _brain.Arbitrate();
                }

                //update the sensory memory with any visual stimulus
                if (_vsionUpdateRegulator.isReady())
                {
                    _sensoryMem.UpdateVision();
                }

                //select the appropriate weapon to use from the weapons currently in
                //the inventory
                if (_weaponSelectionRegulator.isReady())
                {
                    _weaponSystem.SelectWeapon();
                }

                //this method aims the bot's current weapon at the current target
                //and takes a shot if a shot is possible
                _weaponSystem.TakeAimAndShoot();
            }
            UnityEngine.Profiling.Profiler.EndSample();

            _displayObject.Update();
        }

        //------------------------- UpdateMovement ------------------------------------
        //
        //  this method is called from the update method. It calculates and applies
        //  the steering force for this time-step.
        //-----------------------------------------------------------------------------
        void UpdateMovement()
        {
            //calculate the combined steering force
            Vector2 force = _steering.Calculate();

            //if no steering force is produced decelerate the player by applying a
            //braking force
            if (_steering.Force() == Vector2.zero)
            {
                Fix BrakingRate = Fix.Ratio(4, 5);

                _velocity = _velocity * BrakingRate;
            }

            //calculate the acceleration
            Vector2 accel = force / _mass;

            //update the velocity
            _velocity += accel;

            //make sure vehicle does not exceed maximum velocity
            _velocity.Truncate(_maxSpeed);

            //update the position
            _position += _velocity;

            //if the vehicle has a non zero velocity the heading and side vectors must 
            //be updated
            if (_velocity != Vector2.zero)
            {
                _heading = _velocity.normalized;

                _side = _heading.Perp();
            }
        }

        //------------------------------- Spawn ---------------------------------------
        //
        //  spawns the bot at the given position
        //-----------------------------------------------------------------------------
        public void Spawn(Vector2 pos)
        {
            SetAlive();
            _brain.RemoveAllSubgoals();
            _targetSystem.ClearTarget();
            _sensoryMem.ClearMemory();
            SetPos(pos);
            _weaponSystem.Initialize();
            RestoreHealthToMaximum();
        }

        //--------------------------- HandleMessage -----------------------------------
//-----------------------------------------------------------------------------
        public override bool HandleMessage(Telegram msg)
        {
            //first see if the current goal accepts the message
            if (GetBrain().HandleMessage(msg))
            {
                return true;
            }

            //handle any messages not handles by the goals
            switch (msg.msg)
            {
                case MessageType.Msg_TakeThatMF:

                    //just return if already dead or spawning
                    if (isDead() || isSpawning()) return true;

                    //the extra info field of the telegram carries the amount of damage
                    Fix damage = (Fix) msg.extraInfo;
                    ReduceHealth((int) damage);

                    //if this bot is now dead let the shooter know
                    if (isDead())
                    {
                        _world.Dispatcher.DispatchMsg(Constants.SEND_MSG_IMMEDIATELY,
                            ID(),
                            msg.sender,
                            MessageType.Msg_YouGotMeYouSOB);
                    }

                    return true;
                case MessageType.Msg_YouGotMeYouSOB:
                    _targetSystem.ClearTarget();
                    _sensoryMem.RemoveBotFromMemory(_world.EntityManager.GetEntityFromID(msg.sender) as RavenBot);
                    return true;
                case MessageType.Msg_GunshotSound:
                    GetSensoryMem().UpdateWithSoundSource((RavenBot) msg.extraInfo);
                    return true;

                default: return false;
            }
        }

        public Fix FieldOfView()
        {
            return _fieldOfView;
        }

        //------------------ RotateFacingTowardPosition -------------------------------
        //
        //  given a target position, this method rotates the bot's facing vector
        //  by an amount not greater than m_dMaxTurnRate until it
        //  directly faces the target.
        //
        //  returns true when the heading is facing in the desired direction
        //----------------------------------------------------------------------------
        public bool RotateFacingTowardPosition(Vector2 target)
        {
            Vector2 toTarget = (target - _position).normalized;

            Fix dot = Vector2.Dot(_facing, toTarget);

            //clamp to rectify any rounding errors
            dot = FixMath.Clamp(dot, -1, 1);

            //determine the angle between the heading vector and the target
            Fix angle = FixMath.Acos(dot);

            //return true if the bot's facing is within WeaponAimTolerance degs of
            //facing the target
            Fix WeaponAimTolerance = Fix.Ratio(2, 100); //2 degs approx

            if (angle < WeaponAimTolerance)
            {
                _facing = toTarget;
                return true;
            }

            //clamp the amount to turn to the max turn rate
            if (angle > _maxTurnRate) angle = _maxTurnRate;

            //The next few lines use a rotation matrix to rotate the player's facing
            //vector accordingly
            C2DMatrix RotationMatrix = new C2DMatrix();

            //notice how the direction of rotation has to be determined when creating
            //the rotation matrix
            RotationMatrix.Rotate(angle * _facing.Sign(toTarget));
            RotationMatrix.TransformVector2s(ref _facing);
            _facing.Normalize();

            return false;
        }

        public RavenBot GetTargetBot()
        {
            return _targetSystem.GetTarget();
        }

        //------------------------- hasLOSt0 ------------------------------------------
        //
        //  returns true if the bot has line of sight to the given position.
        //-----------------------------------------------------------------------------
        public bool hasLOSto(Vector2 pos)
        {
            return _world.isLOSOkay(Pos(), pos);
        }

        //----------------- CalculateExpectedTimeToReachPosition ----------------------
        //
        //  returns a value indicating the time in seconds it will take the bot
        //  to reach the given position at its current speed.
        //-----------------------------------------------------------------------------
        public Fix CalculateTimeToReachPosition(Vector2 pos)
        {
            return Vector2.Distance(Pos(), pos) / (MaxSpeed() * Constants.FrameRate);
        }

        //------------------------ isAtPosition ---------------------------------------
        //
        //  returns true if the bot is close to the given position
        //-----------------------------------------------------------------------------
        public bool isAtPosition(Vector2 pos)
        {
            Fix tolerance = 10;

            return (Pos() - pos).sqrMagnitude < tolerance * tolerance;
        }

        //---------------------------- isReadyForTriggerUpdate ------------------------
        //
        //  returns true if the bot is ready to be tested against the world triggers
        //-----------------------------------------------------------------------------
        public bool isReadyForTriggerUpdate()
        {
            return _triggerTestRegulator.isReady();
        }

        //returns true if this bot can move directly to the given position
        //without bumping into any walls
        public bool CanWalkTo(Vector2 pos)
        {
            return !_world.IsPathObstructed(Pos(), pos, BRadius());
        }

        //similar to above. Returns true if the bot can move between the two
        //given positions without bumping into any walls
        public bool CanWalkBetween(Vector2 from, Vector2 to)
        {
            return !_world.IsPathObstructed(from, to, BRadius());
        }

        //--------------------------------- ReduceHealth ----------------------------
        public void ReduceHealth(int val)
        {
            _health -= val;

            if (_health <= 0)
            {
                SetDead();
            }
        }

        public void RestoreHealthToMaximum()
        {
            _health = _maxHealth;
        }

        public void IncreaseHealth(int val)
        {
            _health += val;
            _health = FixMath.Clamp(_health, 0, _maxHealth);
        }

        //--------------------------- canStep Methods ---------------------------------
        //
        //  returns true if there is space enough to step in the indicated direction
        //  If true PositionOfStep will be assigned the offset position
        //-----------------------------------------------------------------------------
        public bool CanStepLeft(ref Vector2 PositionOfStep)
        {
            Fix StepDistance = BRadius() * 2;

            PositionOfStep = Pos() - Facing().Perp() * StepDistance - Facing().Perp() * BRadius();

            return CanWalkTo(PositionOfStep);
        }

        public bool CanStepRight(ref Vector2 PositionOfStep)
        {
            Fix StepDistance = BRadius() * 2;

            PositionOfStep = Pos() + Facing().Perp() * StepDistance + Facing().Perp() * BRadius();

            return CanWalkTo(PositionOfStep);
        }
    }
}